﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using System.Collections;
using Slam.SharePoint;
using System.Diagnostics;
using Slam.Configuration;
using System.Web;
using Slam.Logging;
using Microsoft.SharePoint.Administration;

namespace Slam.EventReceivers
{
	public class SlamEventReceiver : SPItemEventReceiver
	{
		/// <summary>
		/// The lock object for synchronizing Creates
		/// </summary>
		internal static readonly object _createLock = new object();

		/// <summary>
		/// The lock object for synchronizing Deletes
		/// </summary>
		internal static readonly object _deleteLock = new object();

		/// <summary>
		/// The lock object for synchronizing Updates
		/// </summary>
		internal static readonly object _updateLock = new object();

		/// <summary>
		/// The lock object for synchronizing Creates
		/// </summary>
		internal static readonly object _synchCreateLock = new object();

		/// <summary>
		/// The lock object for synchronizing Updates
		/// </summary>
		internal static readonly object _synchUpdateLock = new object();

		private HttpContext context;
		public SlamEventReceiver()
		{
			context = HttpContext.Current;
		}

		public override void ItemAdding(SPItemEventProperties properties)
		{
			base.ItemAdding(properties);
			lock (_synchCreateLock)
			{
				HttpContext.Current = context;
				try
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "SLAM PreSave (ItemAdding) called, List: " + properties.ListTitle + ", New ListItem");
					if ((properties.List != null && properties.List.BaseTemplate == SPListTemplateType.DocumentLibrary || properties.List.BaseTemplate == SPListTemplateType.PictureLibrary) &&
						properties.List.Fields.ContainsFieldWithStaticName("SLAM_x0020_ID") && properties.AfterProperties["SLAM ID"] != null)
					{
						properties.AfterProperties["SLAM ID"] = "";
					}

					bool slamItem = true;
					SPContentType fromContentType = null;

					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(properties.SiteId))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[properties.Web.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];
								//Create a dummy list item
								SPListItem elevatedListItem = elevatedList.Items.Add();
								foreach (DictionaryEntry propertyValue in properties.AfterProperties)
								{
									try
									{
										elevatedListItem[propertyValue.Key.ToString()] = propertyValue.Value;
									}
									catch { }
								}

								if (!String.IsNullOrEmpty(properties.ReceiverData))
								{
									slamItem = elevatedListItem.GetSlamConfiguredType().Name == properties.ReceiverData;
								}

								if (elevatedListItem.ContentType != null && !elevatedListItem.ContentType.Parent.Name.Equals("Item"))
								{
									fromContentType = elevatedListItem.ContentType;
								}
							}
						}
					});

					if (slamItem)
					{
						logger.LogMessage("Information", "Start Calling SLAM PreSave (ItemAdding) actions, List: " + properties.ListTitle + ", NewItem");
						SlamContext.Global.ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						SlamContext.Get(properties.List).ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						if (fromContentType != null)
						{
							SlamContext.Get(fromContentType).ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						}
						logger.LogMessage("Information", "Finish Calling SLAM PreSave (ItemAdding) actions, List: " + properties.ListTitle + ", NewItem");
					}
				}

				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog since this is
					//an asynchronous event handler
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});
				}
			}
		}


		public override void ItemUpdating(SPItemEventProperties properties)
		{
			base.ItemUpdating(properties);

			//If this same item is updated several times in quick succession,
			//make sure each SLAM Update finishes before the next one proceeds
			lock (_synchUpdateLock)
			{
				HttpContext.Current = context;
				bool slamItem = true;
				try
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "SLAM PreSave (ItemUpdating) called, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
					if (properties.ListItem.Folder != null && properties.AfterUrl != properties.BeforeUrl)
					{
						SlamItemContext.FolderNameChanged = true;
					}
					
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(properties.SiteId))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[properties.Web.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];

								//Create a dummy list item
								SPListItem elevatedListItem = elevatedList.Items.Add();
								foreach (DictionaryEntry propertyValue in properties.AfterProperties)
								{
									try
									{
										elevatedListItem[propertyValue.Key.ToString()] = propertyValue.Value;
									}
									catch { }
								}
								if (!String.IsNullOrEmpty(properties.ReceiverData))
								{
									slamItem = elevatedListItem.GetSlamConfiguredType().Name == properties.ReceiverData;
								}
							}
						}
					});

					if (slamItem)
					{
						logger.LogMessage("Information", "Start Calling SLAM PreSave (ItemUpdating) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
						SlamContext.Global.ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						SlamContext.Get(properties.List).ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						if (properties.ListItem.ContentType != null && !properties.ListItem.ContentType.Parent.Name.Equals("Item"))
						{
							SlamContext.Get(properties.ListItem.ContentType).ExecuteActions(SlamEvent.PreSave, properties.ListItem);
						}
						logger.LogMessage("Information", "Finish Calling SLAM PreSave (ItemUpdating) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
					}
				}
				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog since this is
					//an asynchronous event handler
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});
				}
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is being deleted from a list with this handler
		/// attached.  This method calls SLAM's IIDeleter.Delete based on this item's ContentType.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemDeleting(SPItemEventProperties properties)
		{
			base.ItemDeleting(properties);
			lock (_deleteLock)
			{
				HttpContext.Current = context;
				bool slamItem = true;

				try
				{
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(properties.SiteId))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[properties.Web.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];
								SPListItem elevatedListItem = null;
								try
								{
									elevatedListItem = elevatedList.GetItemById(properties.ListItemId);
								}
								catch { }

								if (elevatedListItem != null)
								{
									if (!String.IsNullOrEmpty(properties.ReceiverData))
									{
										slamItem = elevatedListItem.GetSlamConfiguredType().Name == properties.ReceiverData;
									}

									if (slamItem)
									{
										SlamContext.Global.ExecuteActions(SlamEvent.Delete, properties.ListItem);
										SlamContext.Get(elevatedList).ExecuteActions(SlamEvent.Delete, properties.ListItem);
										if (elevatedListItem.ContentType != null && !elevatedListItem.ContentType.Parent.Name.Equals("Item"))
										{
											SlamContext.Get(elevatedListItem.ContentType).ExecuteActions(SlamEvent.Delete, properties.ListItem);
										}
									}
								}
							}
						}
					});

					if (slamItem)
					{
						properties.Cancel = !properties.ListItem.SlamDelete(true, false, properties.UserLoginName);
						if (properties.Cancel)
						{
							properties.Status = SPEventReceiverStatus.CancelNoError;
						}
					}

					SlamItemContext.ResetContext();
				}
				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog.  ItemDeleting is
					//syncrhonous unlike the other event handlers, but SLAM should not interrupt
					//a user deleting an item if it fails since SharePoint is not dependent on 
					//SLAM.
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});

					foreach (DictionaryEntry dataItem in ex.Data)
					{
						if (dataItem.Key.ToString() == "CancelDelete" && (bool)dataItem.Value == true)
						{
							properties.Cancel = true;
							properties.ErrorMessage = ex.Message;
							properties.Status = SPEventReceiverStatus.CancelWithError;
							break;
						}
					}
				}
			}
		}

		public override void ItemCheckingIn(SPItemEventProperties properties)
		{
			base.ItemCheckingIn(properties);
			if (properties.ListItem == null)
			{
				ItemAdding(properties);
			}
			else
			{
				ItemUpdating(properties);
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is added to a list with this handler
		/// attached.  This method calls SLAM's IICreator.Create based on this item's ContentType.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemAdded(SPItemEventProperties properties)
		{
			base.ItemAdded(properties);
			lock (_createLock)
			{
				HttpContext.Current = context;
				bool slamItem = true;

				try
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "SLAM PostSave (ItemAdded) called, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(properties.SiteId))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[properties.Web.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];
								SPListItem elevatedListItem = null;
								try
								{
									elevatedListItem = elevatedList.GetItemById(properties.ListItemId);
								}
								catch { }

								if (elevatedListItem != null)
								{
									if (!String.IsNullOrEmpty(properties.ReceiverData))
									{
										slamItem = elevatedListItem.GetSlamConfiguredType().Name == properties.ReceiverData;
									}

									if (slamItem)
									{
										properties.ListItem.SlamUpdate(true, false, false, properties.UserLoginName, true);

										logger.LogMessage("Information", "Start Calling SLAM PreSave (ItemAdded) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
										//It is very likely that SlamUpdate updated this item, make sure to get a new reference
										SPListItem listItem = properties.List.GetItemById(properties.ListItemId);
										SlamContext.Global.ExecuteActions(SlamEvent.PostSave, listItem);
										SlamContext.Get(elevatedList).ExecuteActions(SlamEvent.PostSave, listItem);
										if (elevatedListItem.ContentType != null && !elevatedListItem.ContentType.Parent.Name.Equals("Item"))
										{
											SlamContext.Get(elevatedListItem.ContentType).ExecuteActions(SlamEvent.PostSave, listItem);
										}
										logger.LogMessage("Information", "Finish Calling SLAM PreSave (ItemAdded) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
									}
								}
							}
						}
					});

					SlamItemContext.ResetContext();
				}
				catch (Exception ex)
				{
					//If an Exception is bubbled up to this point, all other logging must have
					//failed in which case we will log the exception in the EventLog since this is
					//an asynchronous event handler
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						if (!EventLog.SourceExists("SLAM"))
						{
							EventLog.CreateEventSource("SLAM", "Application");
						}
						EventLog.WriteEntry("SLAM", ex.ToString(), EventLogEntryType.Error);
					});
				}
			}
		}

		/// <summary>
		/// Event handler executed when an SPListItem is updated in a list with this handler
		/// attached.  This method calls SLAM's IIUpdater.Update based on this item's ContentType.
		/// </summary>
		/// <param name="properties"></param>
		public override void ItemUpdated(SPItemEventProperties properties)
		{
			base.ItemUpdated(properties);

			//If this same item is updated several times in quick succession,
			//make sure each SLAM Update finishes before the next one proceeds
			lock (_updateLock)
			{
				HttpContext.Current = context;
				bool slamItem = true;

				try
				{
					ILogger logger = ComponentService.GetInstance<ILogger>();
					logger.LogMessage("Information", "SLAM PostSave (ItemUpdated) called, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);

					bool resetContext = true;
					SPSecurity.RunWithElevatedPrivileges(delegate()
					{
						using (SPSite elevatedSite = new SPSite(properties.SiteId))
						{
							using (SPWeb elevatedWeb = elevatedSite.AllWebs[properties.Web.ID])
							{
								SPList elevatedList = elevatedWeb.Lists[properties.ListId];
								SPListItem elevatedListItem = null;

								try
								{
									elevatedListItem = elevatedList.GetItemById(properties.ListItemId);
								}
								catch { }

								try
								{
									if (elevatedListItem == null && elevatedList is SPDocumentLibrary)
									{
										foreach (var checkedOutFile in ((SPDocumentLibrary)elevatedList).CheckedOutFiles)
										{
											if (checkedOutFile.ListItemId == properties.ListItemId)
											{
												resetContext = false;
												break;
											}
										}
									}
								}
								catch { }

								if (elevatedListItem != null)
								{
									if (!String.IsNullOrEmpty(properties.ReceiverData))
									{
										slamItem = elevatedListItem.GetSlamConfiguredType().Name == properties.ReceiverData;
									}

									if (slamItem)
									{
										try
										{
											if (properties.BeforeProperties["SLAM ID"] != null && properties.BeforeProperties["SLAM ID"] != properties.AfterProperties["SLAM ID"])
											{
												EventFiringEnabled = false;
												properties.ListItem["SLAM ID"] = properties.BeforeProperties["SLAM ID"];
												properties.Web.AllowUnsafeUpdates = true;
												properties.ListItem.SystemUpdate(false);
												EventFiringEnabled = true;
											}
										}
										catch { }
										properties.ListItem.SlamUpdate(true, false, false, properties.UserLoginName, false);

										logger.LogMessage("Information", "Start Calling SLAM PostSave (ItemUpdated) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
										//It is very likely that SlamUpdate updated this item, make sure to get a new reference
										SPListItem listItem = properties.List.GetItemById(properties.ListItemId);
										SlamContext.Global.ExecuteActions(SlamEvent.PostSave, listItem);
										SlamContext.Get(elevatedList).ExecuteActions(SlamEvent.PostSave, listItem);
										if (elevatedListItem.ContentType != null && !elevatedListItem.ContentType.Parent.Name.Equals("Item"))
										{
											SlamContext.Get(elevatedListItem.ContentType).ExecuteActions(SlamEvent.PostSave, listItem);
										}
										logger.LogMessage("Information", "Finish Calling SLAM PostSave (ItemUpdated) actions, List: " + properties.ListTitle + ", ListItem: " + properties.ListItem.Title);
									}
								}
							}
						}
					});

					if (resetContext)
						SlamItemContext.ResetContext();
				}
				catch (Exception ex)
				{
					//SlamContext.Log.WriteException(ex);
					SPDiagnosticsService.Local.WriteTrace(0, new SPDiagnosticsCategory("Slam Event Receiver - ItemUpdated", TraceSeverity.High, EventSeverity.Error), TraceSeverity.High, ex.ToString(), null);
				}
			}
		}

		public override void ItemCheckedIn(SPItemEventProperties properties)
		{
			base.ItemCheckedIn(properties);
			ItemUpdated(properties);
		}
	}
}
